using System;
using System.IO;
using System.Collections.Generic;
using System.Text;

using PickGold.Zlib;

namespace PickGold.Zip
{
	/// <summary>
	///   Delegate in which the application writes the <c>ZipEntry</c> content for the named entry.
	/// </summary>
	///
	/// <param name="entryName">The name of the entry that must be written.</param>
	/// <param name="stream">The stream to which the entry data should be written.</param>
	///
	/// <remarks>
	///   When you add an entry and specify a <c>WriteDelegate</c>, via <see
	///   cref="PickGold.Zip.ZipFile.AddEntry(string, WriteDelegate)"/>, the application
	///   code provides the logic that writes the entry data directly into the zip file.
	/// </remarks>
	///
	/// <example>
	///
	/// This example shows how to define a WriteDelegate that obtains a DataSet, and then
	/// writes the XML for the DataSet into the zip archive.  There's no need to
	/// save the XML to a disk file first.
	///
	/// <code lang="C#">
	/// private void WriteEntry (String filename, Stream output)
	/// {
	///     DataSet ds1 = ObtainDataSet();
	///     ds1.WriteXml(output);
	/// }
	///
	/// private void Run()
	/// {
	///     using (var zip = new ZipFile())
	///     {
	///         zip.AddEntry(zipEntryName, WriteEntry);
	///         zip.Save(zipFileName);
	///     }
	/// }
	/// </code>
	///
	/// <code lang="vb">
	/// Private Sub WriteEntry (ByVal filename As String, ByVal output As Stream)
	///     DataSet ds1 = ObtainDataSet()
	///     ds1.WriteXml(stream)
	/// End Sub
	///
	/// Public Sub Run()
	///     Using zip = New ZipFile
	///         zip.AddEntry(zipEntryName, New WriteDelegate(AddressOf WriteEntry))
	///         zip.Save(zipFileName)
	///     End Using
	/// End Sub
	/// </code>
	/// </example>
	/// <seealso cref="PickGold.Zip.ZipFile.AddEntry(string, WriteDelegate)"/>
	public delegate void WriteDelegate(string entryName, System.IO.Stream stream);


	/// <summary>
	///   Delegate in which the application opens the stream, just-in-time, for the named entry.
	/// </summary>
	///
	/// <param name="entryName">
	/// The name of the ZipEntry that the application should open the stream for.
	/// </param>
	///
	/// <remarks>
	///   When you add an entry via <see cref="PickGold.Zip.ZipFile.AddEntry(string,
	///   OpenDelegate, CloseDelegate)"/>, the application code provides the logic that
	///   opens and closes the stream for the given ZipEntry.
	/// </remarks>
	///
	/// <seealso cref="PickGold.Zip.ZipFile.AddEntry(string, OpenDelegate, CloseDelegate)"/>
	public delegate Stream OpenDelegate(string entryName);

	/// <summary>
	///   Delegate in which the application closes the stream, just-in-time, for the named entry.
	/// </summary>
	///
	/// <param name="entryName">
	/// The name of the ZipEntry that the application should close the stream for.
	/// </param>
	///
	/// <param name="stream">The stream to be closed.</param>
	///
	/// <remarks>
	///   When you add an entry via <see cref="PickGold.Zip.ZipFile.AddEntry(string,
	///   OpenDelegate, CloseDelegate)"/>, the application code provides the logic that
	///   opens and closes the stream for the given ZipEntry.
	/// </remarks>
	///
	/// <seealso cref="PickGold.Zip.ZipFile.AddEntry(string, OpenDelegate, CloseDelegate)"/>
	public delegate void CloseDelegate(string entryName, Stream stream);

	/// <summary>
	///   Delegate for the callback by which the application tells the
	///   library the CompressionLevel to use for a file.
	/// </summary>
	///
	/// <remarks>
	/// <para>
	///   Using this callback, the application can, for example, specify that
	///   previously-compressed files (.mp3, .png, .docx, etc) should use a
	///   <c>CompressionLevel</c> of <c>None</c>, or can set the compression level based
	///   on any other factor.
	/// </para>
	/// </remarks>
	/// <seealso cref="PickGold.Zip.ZipFile.SetCompression"/>
	public delegate CompressionLevel SetCompressionCallback(string localFileName, string fileNameInArchive);

	/// <summary>
	///   In an EventArgs type, indicates which sort of progress event is being
	///   reported.
	/// </summary>
	/// <remarks>
	///   There are events for reading, events for saving, and events for
	///   extracting. This enumeration allows a single EventArgs type to be sued to
	///   describe one of multiple subevents. For example, a SaveProgress event is
	///   invoked before, after, and during the saving of a single entry.  The value
	///   of an enum with this type, specifies which event is being triggered.  The
	///   same applies to Extraction, Reading and Adding events.
	/// </remarks>
	public enum ZipProgressEventType
	{
		/// <summary>
		/// Indicates that a Add() operation has started.
		/// </summary>
		Adding_Started,

		/// <summary>
		/// Indicates that an individual entry in the archive has been added.
		/// </summary>
		Adding_AfterAddEntry,

		/// <summary>
		/// Indicates that a Add() operation has completed.
		/// </summary>
		Adding_Completed,

		/// <summary>
		/// Indicates that a Read() operation has started.
		/// </summary>
		Reading_Started,

		/// <summary>
		/// Indicates that an individual entry in the archive is about to be read.
		/// </summary>
		Reading_BeforeReadEntry,

		/// <summary>
		/// Indicates that an individual entry in the archive has just been read.
		/// </summary>
		Reading_AfterReadEntry,

		/// <summary>
		/// Indicates that a Read() operation has completed.
		/// </summary>
		Reading_Completed,

		/// <summary>
		/// The given event reports the number of bytes read so far
		/// during a Read() operation.
		/// </summary>
		Reading_ArchiveBytesRead,

		/// <summary>
		/// Indicates that a Save() operation has started.
		/// </summary>
		Saving_Started,

		/// <summary>
		/// Indicates that an individual entry in the archive is about to be written.
		/// </summary>
		Saving_BeforeWriteEntry,

		/// <summary>
		/// Indicates that an individual entry in the archive has just been saved.
		/// </summary>
		Saving_AfterWriteEntry,

		/// <summary>
		/// Indicates that a Save() operation has completed.
		/// </summary>
		Saving_Completed,

		/// <summary>
		/// Indicates that the zip archive has been created in a
		/// temporary location during a Save() operation.
		/// </summary>
		Saving_AfterSaveTempArchive,

		/// <summary>
		/// Indicates that the temporary file is about to be renamed to the final archive
		/// name during a Save() operation.
		/// </summary>
		Saving_BeforeRenameTempArchive,

		/// <summary>
		/// Indicates that the temporary file is has just been renamed to the final archive
		/// name during a Save() operation.
		/// </summary>
		Saving_AfterRenameTempArchive,

		/// <summary>
		/// Indicates that the self-extracting archive has been compiled
		/// during a Save() operation.
		/// </summary>
		Saving_AfterCompileSelfExtractor,

		/// <summary>
		/// The given event is reporting the number of source bytes that have run through the compressor so far
		/// during a Save() operation.
		/// </summary>
		Saving_EntryBytesRead,

		/// <summary>
		/// Indicates that an entry is about to be extracted.
		/// </summary>
		Extracting_BeforeExtractEntry,

		/// <summary>
		/// Indicates that an entry has just been extracted.
		/// </summary>
		Extracting_AfterExtractEntry,

		/// <summary>
		///   Indicates that extraction of an entry would overwrite an existing
		///   filesystem file. You must use
		///   <see cref="ExtractExistingFileAction.InvokeExtractProgressEvent">
		///   ExtractExistingFileAction.InvokeExtractProgressEvent</see> in the call
		///   to <c>ZipEntry.Extract()</c> in order to receive this event.
		/// </summary>
		Extracting_ExtractEntryWouldOverwrite,

		/// <summary>
		///   The given event is reporting the number of bytes written so far for
		///   the current entry during an Extract() operation.
		/// </summary>
		Extracting_EntryBytesWritten,

		/// <summary>
		/// Indicates that an ExtractAll operation is about to begin.
		/// </summary>
		Extracting_BeforeExtractAll,

		/// <summary>
		/// Indicates that an ExtractAll operation has completed.
		/// </summary>
		Extracting_AfterExtractAll,

		/// <summary>
		/// Indicates that an error has occurred while saving a zip file.
		/// This generally means the file cannot be opened, because it has been
		/// removed, or because it is locked by another process.  It can also
		/// mean that the file cannot be Read, because of a range lock conflict.
		/// </summary>
		Error_Saving,
	}


	/// <summary>
	/// Provides information about the progress of a save, read, or extract operation.
	/// This is a base class; you will probably use one of the classes derived from this one.
	/// </summary>
	public class ZipProgressEventArgs : EventArgs
	{
		private int _entriesTotal;
		private bool _cancel;
		private ZipEntry _latestEntry;
		private ZipProgressEventType _flavor;
		private String _archiveName;
		private Int64 _bytesTransferred;
		private Int64 _totalBytesToTransfer;


		internal ZipProgressEventArgs() { }

		internal ZipProgressEventArgs(string archiveName, ZipProgressEventType flavor)
		{
			this._archiveName = archiveName;
			this._flavor = flavor;
		}

		/// <summary>
		/// The total number of entries to be saved or extracted.
		/// </summary>
		public int EntriesTotal
		{
			get { return _entriesTotal; }
			set { _entriesTotal = value; }
		}

		/// <summary>
		/// The name of the last entry saved or extracted.
		/// </summary>
		public ZipEntry CurrentEntry
		{
			get { return _latestEntry; }
			set { _latestEntry = value; }
		}

		/// <summary>
		/// In an event handler, set this to cancel the save or extract
		/// operation that is in progress.
		/// </summary>
		public bool Cancel
		{
			get { return _cancel; }
			set { _cancel = _cancel || value; }
		}

		/// <summary>
		/// The type of event being reported.
		/// </summary>
		public ZipProgressEventType EventType
		{
			get { return _flavor; }
			set { _flavor = value; }
		}

		/// <summary>
		/// Returns the archive name associated to this event.
		/// </summary>
		public String ArchiveName
		{
			get { return _archiveName; }
			set { _archiveName = value; }
		}


		/// <summary>
		/// The number of bytes read or written so far for this entry.
		/// </summary>
		public Int64 BytesTransferred
		{
			get { return _bytesTransferred; }
			set { _bytesTransferred = value; }
		}



		/// <summary>
		/// Total number of bytes that will be read or written for this entry.
		/// This number will be -1 if the value cannot be determined.
		/// </summary>
		public Int64 TotalBytesToTransfer
		{
			get { return _totalBytesToTransfer; }
			set { _totalBytesToTransfer = value; }
		}
	}



	/// <summary>
	/// Provides information about the progress of a Read operation.
	/// </summary>
	public class ReadProgressEventArgs : ZipProgressEventArgs
	{

		internal ReadProgressEventArgs() { }

		private ReadProgressEventArgs(string archiveName, ZipProgressEventType flavor)
			: base(archiveName, flavor)
		{ }

		internal static ReadProgressEventArgs Before(string archiveName, int entriesTotal)
		{
			var x = new ReadProgressEventArgs(archiveName, ZipProgressEventType.Reading_BeforeReadEntry);
			x.EntriesTotal = entriesTotal;
			return x;
		}

		internal static ReadProgressEventArgs After(string archiveName, ZipEntry entry, int entriesTotal)
		{
			var x = new ReadProgressEventArgs(archiveName, ZipProgressEventType.Reading_AfterReadEntry);
			x.EntriesTotal = entriesTotal;
			x.CurrentEntry = entry;
			return x;
		}

		internal static ReadProgressEventArgs Started(string archiveName)
		{
			var x = new ReadProgressEventArgs(archiveName, ZipProgressEventType.Reading_Started);
			return x;
		}

		internal static ReadProgressEventArgs ByteUpdate(string archiveName, ZipEntry entry, Int64 bytesXferred, Int64 totalBytes)
		{
			var x = new ReadProgressEventArgs(archiveName, ZipProgressEventType.Reading_ArchiveBytesRead);
			x.CurrentEntry = entry;
			x.BytesTransferred = bytesXferred;
			x.TotalBytesToTransfer = totalBytes;
			return x;
		}

		internal static ReadProgressEventArgs Completed(string archiveName)
		{
			var x = new ReadProgressEventArgs(archiveName, ZipProgressEventType.Reading_Completed);
			return x;
		}

	}


	/// <summary>
	/// Provides information about the progress of a Add operation.
	/// </summary>
	public class AddProgressEventArgs : ZipProgressEventArgs
	{
		internal AddProgressEventArgs() { }

		private AddProgressEventArgs(string archiveName, ZipProgressEventType flavor)
			: base(archiveName, flavor)
		{ }

		internal static AddProgressEventArgs AfterEntry(string archiveName, ZipEntry entry, int entriesTotal)
		{
			var x = new AddProgressEventArgs(archiveName, ZipProgressEventType.Adding_AfterAddEntry);
			x.EntriesTotal = entriesTotal;
			x.CurrentEntry = entry;
			return x;
		}

		internal static AddProgressEventArgs Started(string archiveName)
		{
			var x = new AddProgressEventArgs(archiveName, ZipProgressEventType.Adding_Started);
			return x;
		}

		internal static AddProgressEventArgs Completed(string archiveName)
		{
			var x = new AddProgressEventArgs(archiveName, ZipProgressEventType.Adding_Completed);
			return x;
		}

	}

	/// <summary>
	/// Provides information about the progress of a save operation.
	/// </summary>
	public class SaveProgressEventArgs : ZipProgressEventArgs
	{
		private int _entriesSaved;

		/// <summary>
		/// Constructor for the SaveProgressEventArgs.
		/// </summary>
		/// <param name="archiveName">the name of the zip archive.</param>
		/// <param name="before">whether this is before saving the entry, or after</param>
		/// <param name="entriesTotal">The total number of entries in the zip archive.</param>
		/// <param name="entriesSaved">Number of entries that have been saved.</param>
		/// <param name="entry">The entry involved in the event.</param>
		internal SaveProgressEventArgs(string archiveName, bool before, int entriesTotal, int entriesSaved, ZipEntry entry)
			: base(archiveName, (before) ? ZipProgressEventType.Saving_BeforeWriteEntry : ZipProgressEventType.Saving_AfterWriteEntry)
		{
			this.EntriesTotal = entriesTotal;
			this.CurrentEntry = entry;
			this._entriesSaved = entriesSaved;
		}

		internal SaveProgressEventArgs() { }

		internal SaveProgressEventArgs(string archiveName, ZipProgressEventType flavor)
			: base(archiveName, flavor)
		{ }


		internal static SaveProgressEventArgs ByteUpdate(string archiveName, ZipEntry entry, Int64 bytesXferred, Int64 totalBytes)
		{
			var x = new SaveProgressEventArgs(archiveName, ZipProgressEventType.Saving_EntryBytesRead);
			x.ArchiveName = archiveName;
			x.CurrentEntry = entry;
			x.BytesTransferred = bytesXferred;
			x.TotalBytesToTransfer = totalBytes;
			return x;
		}

		internal static SaveProgressEventArgs Started(string archiveName)
		{
			var x = new SaveProgressEventArgs(archiveName, ZipProgressEventType.Saving_Started);
			return x;
		}

		internal static SaveProgressEventArgs Completed(string archiveName)
		{
			var x = new SaveProgressEventArgs(archiveName, ZipProgressEventType.Saving_Completed);
			return x;
		}

		/// <summary>
		/// Number of entries saved so far.
		/// </summary>
		public int EntriesSaved
		{
			get { return _entriesSaved; }
		}
	}


	/// <summary>
	/// Provides information about the progress of the extract operation.
	/// </summary>
	public class ExtractProgressEventArgs : ZipProgressEventArgs
	{
		private int _entriesExtracted;
		private string _target;

		/// <summary>
		/// Constructor for the ExtractProgressEventArgs.
		/// </summary>
		/// <param name="archiveName">the name of the zip archive.</param>
		/// <param name="before">whether this is before saving the entry, or after</param>
		/// <param name="entriesTotal">The total number of entries in the zip archive.</param>
		/// <param name="entriesExtracted">Number of entries that have been extracted.</param>
		/// <param name="entry">The entry involved in the event.</param>
		/// <param name="extractLocation">The location to which entries are extracted.</param>
		internal ExtractProgressEventArgs(string archiveName, bool before, int entriesTotal, int entriesExtracted, ZipEntry entry, string extractLocation)
			: base(archiveName, (before) ? ZipProgressEventType.Extracting_BeforeExtractEntry : ZipProgressEventType.Extracting_AfterExtractEntry)
		{
			this.EntriesTotal = entriesTotal;
			this.CurrentEntry = entry;
			this._entriesExtracted = entriesExtracted;
			this._target = extractLocation;
		}

		internal ExtractProgressEventArgs(string archiveName, ZipProgressEventType flavor)
			: base(archiveName, flavor)
		{ }

		internal ExtractProgressEventArgs()
		{ }


		internal static ExtractProgressEventArgs BeforeExtractEntry(string archiveName, ZipEntry entry, string extractLocation)
		{
			var x = new ExtractProgressEventArgs
				{
					ArchiveName = archiveName,
					EventType = ZipProgressEventType.Extracting_BeforeExtractEntry,
					CurrentEntry = entry,
					_target = extractLocation,
				};
			return x;
		}

		internal static ExtractProgressEventArgs ExtractExisting(string archiveName, ZipEntry entry, string extractLocation)
		{
			var x = new ExtractProgressEventArgs
				{
					ArchiveName = archiveName,
					EventType = ZipProgressEventType.Extracting_ExtractEntryWouldOverwrite,
					CurrentEntry = entry,
					_target = extractLocation,
				};
			return x;
		}

		internal static ExtractProgressEventArgs AfterExtractEntry(string archiveName, ZipEntry entry, string extractLocation)
		{
			var x = new ExtractProgressEventArgs
				{
					ArchiveName = archiveName,
					EventType = ZipProgressEventType.Extracting_AfterExtractEntry,
					CurrentEntry = entry,
					_target = extractLocation,
				};
			return x;
		}

		internal static ExtractProgressEventArgs ExtractAllStarted(string archiveName, string extractLocation)
		{
			var x = new ExtractProgressEventArgs(archiveName, ZipProgressEventType.Extracting_BeforeExtractAll);
			x._target = extractLocation;
			return x;
		}

		internal static ExtractProgressEventArgs ExtractAllCompleted(string archiveName, string extractLocation)
		{
			var x = new ExtractProgressEventArgs(archiveName, ZipProgressEventType.Extracting_AfterExtractAll);
			x._target = extractLocation;
			return x;
		}


		internal static ExtractProgressEventArgs ByteUpdate(string archiveName, ZipEntry entry, Int64 bytesWritten, Int64 totalBytes)
		{
			var x = new ExtractProgressEventArgs(archiveName, ZipProgressEventType.Extracting_EntryBytesWritten);
			x.ArchiveName = archiveName;
			x.CurrentEntry = entry;
			x.BytesTransferred = bytesWritten;
			x.TotalBytesToTransfer = totalBytes;
			return x;
		}



		/// <summary>
		/// Number of entries extracted so far.  This is set only if the
		/// EventType is Extracting_BeforeExtractEntry or Extracting_AfterExtractEntry, and
		/// the Extract() is occurring witin the scope of a call to ExtractAll().
		/// </summary>
		public int EntriesExtracted
		{
			get { return _entriesExtracted; }
		}

		/// <summary>
		/// Returns the extraction target location, a filesystem path.
		/// </summary>
		public String ExtractLocation
		{
			get { return _target; }
		}

	}



	/// <summary>
	/// Provides information about the an error that occurred while zipping.
	/// </summary>
	public class ZipErrorEventArgs : ZipProgressEventArgs
	{
		private Exception _exc;
		private ZipErrorEventArgs() { }
		internal static ZipErrorEventArgs Saving(string archiveName, ZipEntry entry, Exception exception)
		{
			var x = new ZipErrorEventArgs
				{
					EventType = ZipProgressEventType.Error_Saving,
					ArchiveName = archiveName,
					CurrentEntry = entry,
					_exc = exception
				};
			return x;
		}

		/// <summary>
		/// Returns the exception that occurred, if any.
		/// </summary>
		public Exception @Exception
		{
			get { return _exc; }
		}

		/// <summary>
		/// Returns the name of the file that caused the exception, if any.
		/// </summary>
		public String FileName
		{
			get { return CurrentEntry.LocalFileName; }
		}
	}


}
